package org.apache.derby.impl.drda;

import org.apache.derby.drda.NetworkServerControl;
import org.apache.derby.iapi.jdbc.DRDAServerStarter;
import org.apache.derby.iapi.services.monitor.Monitor;
import org.apache.derby.iapi.services.property.PropertyUtil;
import org.apache.derby.iapi.tools.i18n.LocalizedResource;
import org.apache.derby.iapi.util.StringUtil;
import org.apache.derby.shared.common.info.ProductGenusNames;
import org.apache.derby.shared.common.info.ProductVersionHolder;
import org.apache.derby.shared.common.reference.DRDAConstants;
import org.apache.derby.shared.common.reference.Property;
import org.apache.derby.shared.common.sanity.SanityManager;

import javax.net.SocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.sql.Driver;
import java.util.*;

/**
 * @author ex_chenzy59
 * @since 2025/3/5
 *
 */
public final class NetworkServerControlImpl {
    private final static int NO_USAGE_MSGS= 12;
    private final static String [] COMMANDS =
            {"start","shutdown","trace","tracedirectory","ping",
                    "logconnections", "sysinfo", "runtimeinfo",  "maxthreads", "timeslice", ""};
    // number of required arguments for each command
    private final static int [] COMMAND_ARGS =
            {0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0};
    public final static int COMMAND_START = 0;
    public final static int COMMAND_SHUTDOWN = 1;
    public final static int COMMAND_TRACE = 2;
    public final static int COMMAND_TRACEDIRECTORY = 3;
    public final static int COMMAND_TESTCONNECTION = 4;
    public final static int COMMAND_LOGCONNECTIONS = 5;
    public final static int COMMAND_SYSINFO = 6;
    public final static int COMMAND_RUNTIME_INFO = 7;
    public final static int COMMAND_MAXTHREADS = 8;
    public final static int COMMAND_TIMESLICE = 9;
    public final static int COMMAND_PROPERTIES = 10;
    public final static int COMMAND_UNKNOWN = -1;
    private final static String [] DASHARGS =
            {"p", "d", "user", "password", "ld", "ea", "ep", "b", "h", "s",
                    "noSecurityManager", "ssl"};
    public final static int DASHARG_PORT = 0;
    public final static int DASHARG_DATABASE = 1;
    public final static int DASHARG_USER = 2;
    public final static int DASHARG_PASSWORD = 3;
    public final static int DASHARG_LOADSYSIBM = 4;
    public final static int DASHARG_ENCALG = 5;
    public final static int DASHARG_ENCPRV = 6;
    public final static int DASHARG_BOOTPASSWORD = 7;
    public final static int DASHARG_HOST = 8;
    public final static int DASHARG_SESSION = 9;
    public final static int DASHARG_UNSECURE = 10;
    private final static int DASHARG_SSL = 11;

    //All the commands except shutdown with username and password are at
    //protocol level 1.
    private final static int DEFAULT_PROTOCOL_VERSION = 1;
    // DERBY-2109: shutdown command now transmits optional user credentials
    //For shutdown with username/password, we have added a new protocol level
    private final static int SHUTDOWN_WITH_CREDENTIAL_PROTOCOL_VERSION = 2;
    //The highest protocol level is 2. The reason for it to be at 2 is
    //the shutdown command with username/password
    private final static int MAX_ALLOWED_PROTOCOL_VERSION = 2;

    private final static String COMMAND_HEADER = "CMD:";
    private final static String REPLY_HEADER = "RPY:";
    private final static int REPLY_HEADER_LENGTH = REPLY_HEADER.length();
    private final static int OK = 0;
    private final static int WARNING = 1;
    private final static int ERROR = 2;
    private final static int SQLERROR = 3;
    private final static int SQLWARNING = 4;

    private final static String DRDA_PROP_MESSAGES = "org.apache.derby.loc.drda.messages";
    private final static String DRDA_PROP_DEBUG = "derby.drda.debug";
    private final static String CLOUDSCAPE_DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";

    public final static String UNEXPECTED_ERR = "Unexpected exception";

    private final static int MIN_MAXTHREADS = -1;
    private final static int MIN_TIMESLICE = -1;
    private final static int USE_DEFAULT = -1;
    private final static int DEFAULT_MAXTHREADS = 0; //for now create whenever needed
    private final static int DEFAULT_TIMESLICE = 0; //for now never yield

    private final static String DEFAULT_HOST = "localhost";
    private final static String DRDA_MSG_PREFIX = "DRDA_";
    private final static String DEFAULT_LOCALE= "en";
    private final static String DEFAULT_LOCALE_COUNTRY="US";

    // Check up to 10 seconds to see if shutdown occurred
    private final static int SHUTDOWN_CHECK_ATTEMPTS = 100;
    private final static int SHUTDOWN_CHECK_INTERVAL= 100;

    /**
     * Maximum reply size. The reply buffer must be large enough to hold the
     * largest reply that {@link #readBytesReply(String)} and
     * {@link #readStringReply(String)} can receive. That is, a reply header
     * (4 bytes), a status byte (1 byte), a length field (2 bytes) and the
     * longest value (in bytes) that could be written by
     * {@link DDMWriter#writeLDBytes(byte[])} or
     * {@link DDMWriter#writeLDString(String)}.
     */
    private final static int MAXREPLY =
            REPLY_HEADER_LENGTH
                    + 1     // status byte
                    + 2     // length field
                    + DDMWriter.MAX_VARCHAR_BYTE_LENGTH;

    // Application Server Attributes.
    private static String att_srvclsnm;
    protected final static String ATT_SRVNAM = "NetworkServerControl";

    private static String att_extnam;
    private static String att_srvrlslv;
    private static String prdId;
    private static byte[] prdIdBytes_;

    private static String buildNumber;
    private static String versionString;
    // we will use single or mixed, not double byte to reduce traffic on the
    // wire, this is in keeping with JCC
    // Note we specify UTF8 for the single byte encoding even though it can
    // be multi-byte.
    protected final static int CCSIDSBC = 1208; //use UTF8
    protected final static int CCSIDMBC = 1208; //use UTF8
    protected final static String DEFAULT_ENCODING = "UTF8"; // use UTF8 for writing
    final static Charset DEFAULT_CHARSET = Charset.forName(DEFAULT_ENCODING);
    protected final static int DEFAULT_CCSID = 1208;
    protected final static byte SPACE_CHAR = 32;


    // Application Server manager levels - this needs to be in sync
    // with CodePoint.MGR_CODEPOINTS
    private final static int [] MGR_LEVELS = { 7, // AGENT
            4, // CCSID Manager
            0, // CNMAPPC not implemented
            0, // CMNSYNCPT not implemented
            5, // CMNTCPIP
            0, // DICTIONARY
            7, // RDB
            0, // RSYNCMGR
            7, // SECMGR
            7, // SQLAM
            0, // SUPERVISOR
            0, // SYNCPTMGR
            1208, // UNICODE Manager
            7  // XAMGR
    };


    private PrintWriter logWriter;                        // console
    private PrintWriter cloudscapeLogWriter;              // derby.log
    private static Driver cloudscapeDriver;

    // error types
    private final static int ERRTYPE_SEVERE = 1;
    private final static int ERRTYPE_USER = 2;
    private final static int ERRTYPE_INFO = 3;
    private final static int ERRTYPE_UNKNOWN = -1;

    // command argument information
    private Vector<String> commandArgs = new Vector<String>();
    private String databaseArg;
    // DERBY-2109: Note that derby JDBC clients have a default user name
    // "APP" (= Property.DEFAULT_USER_NAME) assigned if they don't provide
    // credentials.  We could do the same for NetworkServerControl clients
    // here, but this class is robust enough to allow for null as default.
    private String userArg = null;
    private String passwordArg = null;
    private String bootPasswordArg;
    private String encAlgArg;
    private String encPrvArg;
    private String hostArg = DEFAULT_HOST;
    private InetAddress hostAddress;
    private int sessionArg;
    private boolean unsecureArg;

    // Used to debug memory in SanityManager.DEBUG mode
    private memCheck mc;

    // reply buffer
    private byte [] replyBuffer;
    private int replyBufferCount;   //length of reply
    private int replyBufferPos;     //current position in reply

    //
    // server configuration
    //
    // static values - set at start can't be changed once server has started
    private int portNumber = NetworkServerControl.DEFAULT_PORTNUMBER;   // port server listens to

    // configurable values
    private String traceDirectory;      // directory to place trace files in
    private Object traceDirectorySync = new Object();// object to use for syncing
    private boolean traceAll;           // trace all sessions
    private Object traceAllSync = new Object(); // object to use for syncing reading
    // and changing trace all
    private Object serverStartSync = new Object();  // for syncing start of server.
    private boolean logConnections;     // log connects
    private Object logConnectionsSync = new Object(); // object to use for syncing
    // logConnections value
    private int minThreads;             // default minimum number of connection threads
    private int maxThreads;             // default maximum number of connection threads
    private Object threadsSync = new Object(); // object to use for syncing reading
    // and changing default min and max threads
    private int timeSlice;              // default time slice of a session to a thread
    private Object timeSliceSync = new Object();// object to use for syncing reading
    // and changing timeSlice

    private boolean keepAlive = true;   // keepAlive value for client socket
    private int minPoolSize;            //minimum pool size for pooled connections
    private int maxPoolSize;            //maximum pool size for pooled connections
    private Object poolSync = new Object(); // object to use for syning reading

    private boolean debugOutput = false;
    private boolean cleanupOnStart = false; // Should we clean up when starting the server?
    private boolean restartFlag = false;

    protected final static int INVALID_OR_NOTSET_SECURITYMECHANISM = -1;
    // variable to store value set to derby.drda.securityMechanism
    // default value is -1 which indicates that this property isnt set or
    // the value is invalid
    private int allowOnlySecurityMechanism = INVALID_OR_NOTSET_SECURITYMECHANISM;
    //
    // variables for a client command session
    //
    private Socket clientSocket = null;
    private InputStream clientIs = null;
    private OutputStream clientOs = null;
    private ByteArrayOutputStream byteArrayOs = new ByteArrayOutputStream();
    private DataOutputStream commandOs = new DataOutputStream(byteArrayOs);

    private Object shutdownSync = new Object();
    private boolean shutdown;
    private int connNum;        // number of connections since server started
    private ServerSocket serverSocket;
    private NetworkServerControlImpl serverInstance;
    private LocalizedResource langUtil;
    private String clientLocale;
    /** List of local addresses for checking admin commands. */
    ArrayList<InetAddress> localAddresses;

    // open sessions
    private Hashtable<Integer, Session> sessionTable =
            new Hashtable<Integer, Session>();

    // current session
    private Session currentSession;
    // DRDAConnThreads
    private Vector<DRDAConnThread> threadList = new Vector<DRDAConnThread>();

    // queue of sessions waiting for a free thread - the queue is managed
    // in a simple first come, first serve manner - no priorities
    private Vector<Session> runQueue = new Vector<Session>();

    // number of DRDAConnThreads waiting for something to do
    private int freeThreads;

    // known application requesters
    private Hashtable<String, AppRequester> appRequesterTable =
            new Hashtable<String, AppRequester>();

    // accessed by inner classes for privileged action
    private String propertyFileName;
    private NetworkServerControlImpl thisControl = this;

    // if the server is started from the command line, it should shutdown the
    // databases it has booted.
    private boolean shutdownDatabasesOnShutdown = false;

    // SSL related stuff
    private static final int SSL_OFF = 0;
    private static final int SSL_BASIC = 1;
    private static final int SSL_PEER_AUTHENTICATION = 2;

    private int sslMode = SSL_OFF;

    /**
     * Can EUSRIDPWD security mechanism be used with
     * the current JVM
     */
    private static boolean SUPPORTS_EUSRIDPWD = false;

    public NetworkServerControlImpl() throws Exception {
        init();
        getPropertyInfo();
    }

    public NetworkServerControlImpl(InetAddress address, int portNumber) throws Exception {
        this();
        this.hostAddress = address;
        this.portNumber = (portNumber <= 0) ? this.portNumber : portNumber;
        this.hostArg = address.getHostAddress();
    }

    public NetworkServerControlImpl(String userName, String password) throws Exception {
        this();
        this.userArg = userName;
        this.passwordArg = password;
    }

    public NetworkServerControlImpl(InetAddress address, int portNumber, String userName, String password) throws Exception {
        this(address, portNumber);
        this.userArg = userName;
        this.passwordArg = password;
    }

    public void ping() throws Exception {
        try {
            setUpSocket();
            pingWithNoOpen();
        } finally {
            closeSocket();
        }
    }

    public void start(PrintWriter consoleWriter) throws Exception {
        var starter = new DRDAServerStarter();
        starter.setStartInfo(hostAddress, portNumber, consoleWriter);
        this.setLogWriter(consoleWriter);
        startNetworkServer();
        starter.boot(false, null);
    }

    private void startNetworkServer() throws Exception {

        // we start the Derby server here.
        boolean restartCheck = this.restartFlag;
        synchronized (serverStartSync) {
            if (restartCheck == this.restartFlag) {
                // then we can go ahead and restart the server (odds
                // that some else has just done so are very slim (but not
                // impossible--however, even if it does happen, things
                // should still work correctly, just not as efficiently...))
                try {
                    if (cleanupOnStart) {
                        // we're restarting the server (probably after a shutdown
                        // exception), so we need to clean up first.
                        // Close and remove sessions on runQueue.
                        synchronized (runQueue) {
                            for (Session s : runQueue) {
                                s.close();
                                removeFromSessionTable(s.getConnNum());
                            }
                            runQueue.clear();
                        }

                        // DERBY-1326: There could be active threads that
                        // contain old/invalid sessions. These sessions won't
                        // be cleaned up until there is some activity on
                        // them. We could optimize this by going through
                        // sessionTable and closing the sessions' socket
                        // streams.

                        // Unload driver, then restart the server.
                        cloudscapeDriver = null;    // so it gets collected.
                        System.gc();
                    }
                    // start the server.
                    Class<?> clazz = Class.forName(CLOUDSCAPE_DRIVER);
                    cloudscapeDriver = (Driver) clazz.getConstructor().newInstance();
                } catch (Exception e) {
                    this.consoleExceptionPrintTrace(e);
                    consolePropertyMessage("DRDA_LoadException.S", e.getMessage());
                }
                cleanupOnStart = true;
                this.restartFlag = !this.restartFlag;
            }
            // else, multiple threads hit this synchronize block at the same
            // time, but one of them already executed it--so all others just
            // return and do nothing (no need to restart the server multiple
            // times in a row).
        }
    }

    public void consoleMessage(String msg, boolean printTimeStamp) {
        // print to console if we have one
        PrintWriter lw = logWriter;
        if (lw != null) {
            synchronized (lw) {
                lw.println(printTimeStamp ? (STR."\{new Date()} : \{msg}") : msg);
            }
        }
        // always print to derby.log
        lw = cloudscapeLogWriter;
        if (lw != null) {
            synchronized (lw) {
                Monitor.logMessage(printTimeStamp ? STR."\{new Date()} : \{msg}" : msg);
            }
        }

    }

    public void consoleExceptionPrintTrace(Throwable e) {
        consoleMessage(e.getMessage(), true);
        PrintWriter lw = logWriter;
        if (lw != null) {
            synchronized (lw) {
                e.printStackTrace(lw);
            }
        }
        // DERBY-5610 - If there is no log writer, only print
        // exception to System.out if derby.drda.debug=true
        else if (debugOutput) {
            e.printStackTrace();
        }

        lw = cloudscapeLogWriter;
        if (lw != null) {
            synchronized (lw) {
                e.printStackTrace(lw);
            }
        }
    }

    private void removeFromSessionTable(int sessionid) {
        sessionTable.remove(sessionid);
    }

    public void setLogWriter(PrintWriter outWriter) {
        // wrap the user-set outWriter with, autoflush to true.
        // this will ensure that messages to console will be
        // written out to the outWriter on a println.
        // DERBY-1466
        logWriter = outWriter != null ? new PrintWriter(outWriter, true) : null;
    }

    public void logConnections(boolean on) throws Exception {
        try {
            setUpSocket();
            writeCommandHeader(COMMAND_LOGCONNECTIONS);
            writeByte(on ? 1 : 0);
            send();
            readResult();
        } finally {
            closeSocket();
        }
    }

    private void writeByte(int value) throws Exception {
        try {
            commandOs.writeByte((byte) (value & 0x0f));
        } catch (IOException e) {
            clientSocketError(e);
        }
    }

    private void closeSocket() throws IOException {
        try {
            if (clientIs != null)
                clientIs.close();
            if (clientOs != null)
                clientOs.close();
            if (clientSocket != null)
                clientSocket.close();
        } finally {
            clientIs = null;
            clientOs = null;
            clientSocket = null;
        }
    }

    public void trace(boolean on) throws Exception {
        trace(0, on);
    }

    public void trace(int connNum, boolean on) throws Exception {
        try {
            setUpSocket();
            writeCommandHeader(COMMAND_TRACE);
            commandOs.writeInt(connNum);
            writeByte(on ? 1 : 0);
            send();
            readResult();
            consoleTraceMessage(connNum, on);
        } finally {
            closeSocket();
        }

    }

    private void consoleTraceMessage(int connNum, boolean on) throws Exception {
        String messageID;
        String[] args = null;
        if (connNum == 0) {
            messageID = on?"DRDA_TraceChangeAllOn.I":"DRDA_TraceChangeAllOff.I";
        } else {
            messageID = on?"DRDA_TraceChangeOneOn.I":"DRDA_TraceChangeOneOff.I";
            args = new String[]{Integer.toString(connNum)};
        }
        consolePropertyMessage(messageID, args);
    }

    private void clientSocketError(IOException e) throws IOException {
        try {
            consolePropertyMessage("DRDA_ClientSocketError.S", e.getMessage());
        } catch (Exception ce) {
        } // catch the exception consolePropertyMessage will
        // throw since we also want to print a stack trace
        consoleExceptionPrintTrace(e);
        throw e;
    }

    private void init() throws Exception {

        final String codeSet = PropertyUtil.getSystemProperty(LocalizedResource.ENV_CODESET);
        final String locale =PropertyUtil.getSystemProperty(LocalizedResource.ENV_LOCALE);
        // adjust the application in accordance with derby.ui.locale and derby.ui.codeset
        langUtil = new LocalizedResource(codeSet, locale, DRDA_PROP_MESSAGES);

        serverInstance = this;

        //set Server attributes to be used in EXCSAT
        var myPVH = getNetProductVersionHolder();
        att_extnam = STR."\{ATT_SRVNAM} \{Thread.currentThread().getName()}";

        att_srvclsnm = myPVH.getProductName();
        versionString = myPVH.getVersionBuildString(true);

        String majorStr = String.valueOf(myPVH.getMajorVersion());
        String minorStr = String.valueOf(myPVH.getMinorVersion());
        // Maintenance version. Server protocol version.
        // Only changed if client needs to recognize a new server version.
        String drdaMaintStr = String.valueOf(myPVH.getDrdaMaintVersion());

        // PRDID format as JCC expects it: CSSMMmx
        // MM = major version
        // mm = minor version
        // x = drda MaintenanceVersion

        //

        prdId = DRDAConstants.DERBY_DRDA_SERVER_ID;
        if (majorStr.length() == 1)
            prdId += "0";
        prdId += majorStr;

        if (minorStr.length() == 1)
            prdId += "0";

        prdId += minorStr;

        prdId += drdaMaintStr;
        att_srvrlslv = prdId + "/" + myPVH.getVersionBuildString(true);
        // Precompute this to save some cycles
        prdIdBytes_ = prdId.getBytes(DEFAULT_ENCODING);

        if (SanityManager.DEBUG) {
            if (majorStr.length() > 2 ||
                    minorStr.length() > 2 ||
                    drdaMaintStr.length() > 1)
                SanityManager.THROWASSERT("version values out of expected range  for PRDID");
        }

        buildNumber = myPVH.getBuildNumber();
    }

    private ProductVersionHolder getNetProductVersionHolder() throws Exception {
        try {
            return ProductVersionHolder.getProductVersionHolderFromMyEnv(getClass().getResourceAsStream(STR."/\{ProductGenusNames.NET_INFO}"));
        } catch (Exception e1) {
            consolePropertyMessage("DRDA_ProductVersionReadError.S", e1.getMessage());
            return null;
        }
    }
    private void setLogConnections(boolean value)
    {
        synchronized(logConnectionsSync) {
            logConnections = value;
        }
        // update the value in all the threads
        synchronized(threadList) {
            for (DRDAConnThread thread : threadList)
            {
                thread.setLogConnections(value);
            }
        }
    }
    private void getPropertyInfo() throws Exception {
        //set values according to properties

        String directory = PropertyUtil.getSystemProperty(Property.SYSTEM_HOME_PROPERTY);
        String propval = PropertyUtil.getSystemProperty(
                Property.DRDA_PROP_LOGCONNECTIONS);
        if (propval != null && StringUtil.SQLEqualsIgnoreCase(propval, "true"))
            setLogConnections(true);
        propval = PropertyUtil.getSystemProperty(Property.DRDA_PROP_TRACEALL);
        if (propval != null && StringUtil.SQLEqualsIgnoreCase(propval,
                "true"))
            setTraceAll(true);

        //If the derby.system.home property has been set, it is the default.
        //Otherwise, the default is the current directory.
        //If derby.system.home is not set, directory will be null and trace files will get
        //created in current directory.
        propval = PropertyUtil.getSystemProperty(Property.DRDA_PROP_TRACEDIRECTORY, directory);
        if (propval != null) {
            if (propval.equals(""))
                propval = directory;
            setTraceDirectory(propval);
        }

        //DERBY-375 If a system property is specified without any value, getProperty returns
        //an empty string. Use default values in such cases.
        propval = PropertyUtil.getSystemProperty(
                Property.DRDA_PROP_MINTHREADS);
        if (propval != null) {
            if (propval.equals(""))
                propval = "0";
            setMinThreads(getIntPropVal(Property.DRDA_PROP_MINTHREADS, propval));
        }

        propval = PropertyUtil.getSystemProperty(
                Property.DRDA_PROP_MAXTHREADS);
        if (propval != null) {
            if (propval.equals(""))
                propval = "0";
            setMaxThreads(getIntPropVal(Property.DRDA_PROP_MAXTHREADS, propval));
        }


        propval = PropertyUtil.getSystemProperty(
                Property.DRDA_PROP_TIMESLICE);
        if (propval != null) {
            if (propval.equals(""))
                propval = "0";
            setTimeSlice(getIntPropVal(Property.DRDA_PROP_TIMESLICE, propval));
        }

        propval = PropertyUtil.getSystemProperty(
                Property.DRDA_PROP_PORTNUMBER);
        if (propval != null) {
            if (propval.equals(""))
                propval = String.valueOf(NetworkServerControl.DEFAULT_PORTNUMBER);
            portNumber = getIntPropVal(Property.DRDA_PROP_PORTNUMBER, propval);
        }

        propval = PropertyUtil.getSystemProperty(
                Property.DRDA_PROP_SSL_MODE);
        setSSLMode(getSSLModeValue(propval));

        propval = PropertyUtil.getSystemProperty(
                Property.DRDA_PROP_KEEPALIVE);
        if (propval != null &&
                StringUtil.SQLEqualsIgnoreCase(propval, "false"))
            keepAlive = false;

        propval = PropertyUtil.getSystemProperty(
                Property.DRDA_PROP_HOSTNAME);
        if (propval != null) {
            if (propval.equals(""))
                hostArg = DEFAULT_HOST;
            else
                hostArg = propval;
        }
        propval = PropertyUtil.getSystemProperty(
                NetworkServerControlImpl.DRDA_PROP_DEBUG);
        if (propval != null && StringUtil.SQLEqualsIgnoreCase(propval, "true"))
            debugOutput = true;

        propval = PropertyUtil.getSystemProperty(
                Property.DRDA_PROP_SECURITYMECHANISM);
        if (propval != null) {
            setSecurityMechanism(propval);
        }

    }

    public void sendSetTraceDirectory(String traceDirectory)
            throws Exception {
        try {
            setUpSocket();
            writeCommandHeader(COMMAND_TRACEDIRECTORY);
            writeLDString(traceDirectory);
            send();
            readResult();
        } finally {
            closeSocket();
        }

    }

    private void setUpSocket() throws Exception {

        try {
            if (hostAddress == null)
                hostAddress = InetAddress.getByName(hostArg);

            switch (getSSLMode()) {
                case SSL_BASIC:
                    Properties sslProperties = getSSLProperties();
                    SSLSocket s1 = (SSLSocket)
                            NaiveTrustManager.getSocketFactory(sslProperties).
                                    createSocket(hostAddress, portNumber);
                    //DERBY-6764(analyze impact of poodle security alert on
                    // Derby client - server ssl support)
                    String[] removeTwoProtocols =
                            removeSSLv3andSSLv2Hello(s1.getEnabledProtocols());
                    s1.setEnabledProtocols(
                            removeTwoProtocols);
                    // Need to handshake now to get proper error reporting.
                    s1.startHandshake();
                    clientSocket = s1;

                case SSL_PEER_AUTHENTICATION:
                    SSLSocket s2 = (SSLSocket) SSLSocketFactory.getDefault().
                            createSocket(hostAddress, portNumber);
                    //DERBY-6764(analyze impact of poodle security alert on
                    // Derby client - server ssl support)
                    removeTwoProtocols =
                            removeSSLv3andSSLv2Hello(s2.getEnabledProtocols());
                    s2.setEnabledProtocols(
                            removeTwoProtocols);
                    // Need to handshake now to get proper error reporting.
                    s2.startHandshake();
                    clientSocket = s2;

                case SSL_OFF:
                default:
                    clientSocket = SocketFactory.getDefault().
                            createSocket(hostAddress, portNumber);
            }
        } catch (Exception e1) {
            if (e1 instanceof UnknownHostException) {
                consolePropertyMessage("DRDA_UnknownHost.S", hostArg);
            } else if (e1 instanceof IOException) {
                consolePropertyMessage("DRDA_NoIO.S",
                        new String[]{
                                hostArg,
                                Integer.toString(portNumber),
                                e1.getMessage()
                        });
            } else {
                throwUnexpectedException(e1);
            }
        }

        try {
            clientIs = clientSocket.getInputStream();
            clientOs = clientSocket.getOutputStream();
        } catch (IOException e) {
            consolePropertyMessage("DRDA_NoInputStream.I", true);
            throw e;
        }
    }

    public void setClientLocale(String locale) {
        clientLocale = locale;
    }

    public Properties getCurrentProperties()
            throws Exception {
        try {
            setUpSocket();
            writeCommandHeader(COMMAND_PROPERTIES);
            send();
            byte[] val = readBytesReply("DRDA_PropertyError.S");

            Properties p = new Properties();
            try {
                ByteArrayInputStream bs = new ByteArrayInputStream(val);
                p.load(bs);
            } catch (IOException io) {
                consolePropertyMessage("DRDA_IOException.S", io.getMessage());
            }
            return p;
        } finally {
            closeSocket();
        }
    }

    public void netSetMaxThreads(int max) throws Exception {
        try {
            setUpSocket();
            writeCommandHeader(COMMAND_MAXTHREADS);
            commandOs.writeInt(max);
            send();
            readResult();
            int newval = readInt();
            consolePropertyMessage("DRDA_MaxThreadsChange.I",
                    String.valueOf(newval));
        } finally {
            closeSocket();
        }

    }

    public void netSetTimeSlice(int timeslice)
            throws Exception {
        try {
            setUpSocket();
            writeCommandHeader(COMMAND_TIMESLICE);
            commandOs.writeInt(timeslice);
            send();
            readResult();
            int newval = readInt();
            consolePropertyMessage("DRDA_TimeSliceChange.I",
                    String.valueOf(newval));
        } finally {
            closeSocket();
        }
    }

    private void writeCommandHeader(int command) throws Exception {
        writeCommandHeader(command, DEFAULT_PROTOCOL_VERSION);
    }

    public void shutdown()
            throws Exception {
        // Wait up to 10 seconds for things to really shut down
        // need a quiet ping so temporarily disable the logwriter
        PrintWriter savWriter;
        int ntry;
        try {
            setUpSocket();
            try {
                writeCommandHeader(COMMAND_SHUTDOWN, SHUTDOWN_WITH_CREDENTIAL_PROTOCOL_VERSION);
                // DERBY-2109: transmit user credentials for System Privileges check
                writeLDString(userArg);
                writeLDString(passwordArg);
                send();
                readResult();
            } catch (Exception e) {
                //The shutdown command with protocol level 2 failed. If
                //the username or password were supplied then we can't
                //try the shutdown with protocol level 1 because protocol
                //leve 1 does not support username/password. Because of
                //that, we should simply throw the caught exception to the
                //client
                if (userArg != null || passwordArg != null)
                    throw e;
                //If no username and password is specified then we can try
                //shutdown with the old protocol level of 1 which is the
                //default protocol level. But this can be tried only if the
                //exception for attempt of shutdown with protocol level 2
                //was DRDA_InvalidReplyHead. This can happen if we are
                //dealing with an older Network server product which do not
                //recognize shutdown at protocol level 2.
                if (e.getMessage().indexOf("DRDA_InvalidReplyHead") != -1) {
                    try {
                        closeSocket();
                        setUpSocket();
                        writeCommandHeader(COMMAND_SHUTDOWN);
                        send();
                        readResult();
                    } catch (Exception e1) {
                        e1.initCause(e);
                        throw e1;
                    }
                } else
                    throw e;
            }
            savWriter = logWriter;
            // DERBY-1571: If logWriter is null, stack traces are printed to
            // System.err. Set logWriter to a silent stream to suppress stack
            // traces too.
            FilterOutputStream silentStream = new FilterOutputStream(null) {
                public void write(int b) {
                }

                public void flush() {
                }

                public void close() {
                }
            };
            setLogWriter(new PrintWriter(silentStream));
            for (ntry = 0; ntry < SHUTDOWN_CHECK_ATTEMPTS; ntry++) {
                Thread.sleep(SHUTDOWN_CHECK_INTERVAL);
                try {
                    pingWithNoOpen();
                } catch (Exception e) {
                    // as soon as we can't ping return
                    break;
                }
            }
        } finally {
            closeSocket();
        }

        if (ntry == SHUTDOWN_CHECK_ATTEMPTS)
            consolePropertyMessage("DRDA_ShutdownError.S", new String[]{
                    Integer.toString(portNumber),
                    hostArg});

        logWriter = savWriter;
    }

    private void pingWithNoOpen() throws Exception {

        // database no longer used, but don't change the protocol
        // in case we add
        // authorization  later.
        String database = null; // no longer used but don't change the protocol
        String user = null;
        String password = null;

        try {
            writeCommandHeader(COMMAND_TESTCONNECTION);
            writeLDString(database);
            writeLDString(user);
            writeLDString(password);
            send();
            readResult();
        } catch (IOException ioe) {
            consolePropertyMessage("DRDA_NoIO.S",
                    new String[]{hostArg,
                            Integer.toString(portNumber),
                            ioe.getMessage()});
        }
    }

    private void consolePropertyMessage(String msgProp, String arg) throws Exception {
        consolePropertyMessageWork(msgProp, new String[]{arg}, true);
    }

    private void consolePropertyMessage(String msgProp, String[] args)
            throws Exception {
        consolePropertyMessageWork(msgProp, args, true);
    }

    private void consolePropertyMessage(String msgProp, boolean printTimeStamp)
            throws Exception {
        consolePropertyMessageWork(msgProp, null, printTimeStamp);
    }

    private void readResult() throws Exception {
        fillReplyBuffer();
        readCommandReplyHeader();
        if (replyBufferPos >= replyBufferCount)
            consolePropertyMessage("DRDA_InvalidReplyTooShort.S", true);
        int messageType = replyBuffer[replyBufferPos++] & 0xFF;
        if (messageType == OK)      // O.K.
            return;
        // get error and display and throw exception
        String message = readLDString();
        if (messageType == SQLERROR)
            wrapSQLError(message);
        else if (messageType == SQLWARNING)
            wrapSQLWarning(message);
        else
            consolePropertyMessage(message, true);
    }

    private void readCommandReplyHeader() throws Exception {
        ensureDataInBuffer(REPLY_HEADER_LENGTH, false);
        if (replyBufferCount < REPLY_HEADER_LENGTH) {
            consolePropertyMessage("DRDA_InvalidReplyHeader1.S", Integer.toString(replyBufferCount));
        }
        String header = new String(replyBuffer, 0, REPLY_HEADER_LENGTH, DEFAULT_ENCODING);
        if (!header.equals(REPLY_HEADER)) {
            consolePropertyMessage("DRDA_InvalidReplyHeader2.S", header);
        }
        replyBufferPos += REPLY_HEADER_LENGTH;
    }

    private void fillReplyBuffer() throws Exception {
        if (replyBuffer == null)
            replyBuffer = new byte[MAXREPLY];
        try {
            replyBufferCount = clientIs.read(replyBuffer);
        } catch (IOException e) {
            clientSocketError(e);
        }
        if (replyBufferCount == -1)
            consolePropertyMessage("DRDA_InvalidReplyTooShort.S", true);
        replyBufferPos = 0;
    }

    private void writeLDString(String msg) throws Exception {
        try {
            if (msg == null) {
                commandOs.writeShort(0);
            } else {
                byte[] msgBytes = msg.getBytes(DEFAULT_ENCODING);
                commandOs.writeShort(msgBytes.length);
                commandOs.write(msgBytes);
            }
        } catch (IOException e) {
            clientSocketError(e);
        }
    }

    private void send() throws Exception {
        try {
            byteArrayOs.writeTo(clientOs);
            commandOs.flush();
            byteArrayOs.reset();    //discard anything currently in the byte array
        } catch (IOException e) {
            clientSocketError(e);
        }
    }

    private int getMessageType(String msg) {
        //all property messages should start with DRDA_
        if (!msg.startsWith(DRDA_MSG_PREFIX)) {
            return ERRTYPE_UNKNOWN;
        }
        int startpos = msg.indexOf('.') + 1;
        if (msg.length() <= startpos) {
            return ERRTYPE_UNKNOWN;
        }
        if (msg.length() > (startpos + 1)) {
            return ERRTYPE_UNKNOWN;
        }
        char type = msg.charAt(startpos);

        return switch (type) {
            case 'S' -> ERRTYPE_SEVERE;
            case 'U' -> ERRTYPE_USER;
            case 'I' -> ERRTYPE_INFO;
            default -> ERRTYPE_UNKNOWN;
        };
    }

    private boolean isMsgProperty(String msg) {
        return msg != null && msg.startsWith(DRDA_MSG_PREFIX);
    }

    private String localizeMessage(String msgProp, LocalizedResource localLangUtil, String[] args) {
        Object[] argMsg = null;
        //check if the argument is a property
        if (args != null) {
            argMsg = new String[args.length];
            for (int i = 0; i < args.length; i++) {
                argMsg[i] = isMsgProperty(args[i]) ? localLangUtil.getTextMessage(args[i]) : args[i];
            }
        }
        return localLangUtil.getTextMessage(msgProp, argMsg);
    }

    private void consolePropertyMessageWork(String messageKey, String[] args, boolean printTimeStamp) throws Exception {
        int type = getMessageType(messageKey);
        var locMsg = type == ERRTYPE_UNKNOWN ? messageKey : localizeMessage(messageKey, langUtil, args);
        //display on the console
        consoleMessage(locMsg, printTimeStamp);

        //if it is a user error display usage
        if (type == ERRTYPE_USER)
            usage();

        //we may want to use a different locale for throwing the exception
        //since this can be sent to a browser with a different locale
        if (currentSession != null &&
                currentSession.langUtil != null &&
                type != ERRTYPE_UNKNOWN)
            locMsg = localizeMessage(messageKey, currentSession.langUtil, args);

        // throw an exception for severe and user errors
        if (type == ERRTYPE_SEVERE || type == ERRTYPE_USER) {
            if (messageKey.equals("DRDA_SQLException.S"))
                throwSQLException(args[0]);
            else if (messageKey.equals("DRDA_SQLWarning.I"))
                throwSQLWarning(args[0]);
            else
                throw new Exception(messageKey + ":" + locMsg);
        }

        // throw an exception with just the message if the error type is
        // unknown
        if (type == ERRTYPE_UNKNOWN)
            throw new Exception(locMsg);

    }
/**
 陈平不记得6岁之前的任何记忆,最早的记忆是电视剧<<春光灿烂猪八戒>>,做梦被猫妖惊吓过度导致惊厥,之后大病一场,从此体弱多病. 实际上,那天晚上,陈平几乎死过一次. 被猫妖逼到绝境后,分裂成两个人格,恶念人格汇聚成为第二人格,保护住了陈平.
 陈平是这个梦境世界的主人,却无法控制这个世界,从还记得的记忆开始,6岁开始,每天睡觉都被恶魔折磨.陈平晚上睡着做的梦必然是清醒梦,梦中会出现看过的影视作品中角色,反面角色一心要吃他,正面角色会救他,但是不久就会堕落成恶.所以他不敢再看影视作品,不敢刷短视频. 不敢胡思乱想.
 小猴子下山/乌鸦喝水/小壁虎借尾巴/雪孩子/拔萝卜/喝水不忘挖井人/小公鸡与小鸭子/咕咚/动物王国开大会/胖乎乎的小手/小白兔和小灰兔/丑小鸭/坐井观天/我要的是葫芦/
 浅水里的小鱼/称象/寒号鸟/大禹治水/狐狸分奶酪/纸船和风筝/风娃娃/村居/古诗二首之,村居,咏柳/《找春天》/《开满鲜花的小路》/邓小平爷爷植树/雷锋叔叔，你在哪里/千人糕/一匹出色的马
 小蝌蚪找妈妈/植物妈妈有办法/彩色的梦/枫树上的喜鹊/寓言二则之亡羊补牢,揠苗助长
 小学中学语文课本上的所有故事里的人物都奉献了自己,拯救了陈平,但是梦境中怪物越来越强,自己却感觉越来越弱(实际上没有变弱,只是长久的紧绷让他精神压力很大),终于18岁高中毕业的陈平,终于打造了自己的安全屋,邪魔无法进入,陈平也就有了喘息之机.
 每天晚上安全屋的位置都可以改变,陈平可以控制在远离堕物的地方随机出现.
 一开始,陈平还敢出去探索,但是经过几次重伤死里逃生后,他就不敢出去了.最起码得等自己更强才行.
 大学四年,陈平清心寡欲,只做两件事,学习,睡觉. 梦中都在加固自己的安全屋. 勉强达到平衡,后面,陈平开始恋爱,然后梦境不可控了,怪物越来越多.毕业被分手时,安全屋甚至被攻破,陈平奄奄一息,被送到重症监护室(无菌),陈平开始看小说,打开了<盘龙>,靠着盘龙的奉献,度过难关,重新开始加固安全屋.
 陈平毕业成为程序员,无聊时在梦中编程.从或与非开始,无中生有手搓出第一代简易的图灵完备的计算机,之后基础电路,运算器,寄存器,存储器,中央处理器,,然后是汇编语言编译器,C语言编译器,最后操作系统.升级成第二代传统计算机系统.在这期间,创造了全新的编程语言Ping1.0,可以取代C,C++,C#,Java,Python等后端语言,和Html,JS,CSS等前端语言.
 做完这些之后,陈平已经毕业5年了.陈平感觉计算机在他眼中已无任何秘密,自己就是计算机领域的神,(当然只是他自己这么认为,其实计算机网络和人工智能这两个方向他还一窍不通),不想再为实现傻缺公司的傻缺需求,用傻缺的代码编辑器,遵循傻缺的编程语言写那些傻缺代码.
 陈平果断离职,接些外快够自己日常开销就行,开始设计自己的计算机系统,没有现实的电路约束,陈平打算把计算机改造成10进制的计算机.原架构从基础电路开始彻底重构,Ping语言也进行升级成为Ping2.0,专门编写10进制计算机系统上运行的软件,然后用Ping语音开发了些常用的系统软件,如数据库,浏览器,多媒体播放器等.
 Ping语言编译器可以把Ping语言编译成传统的汇编语言,C,C++,C#,Java,Python等,然后开始研究AI,把开源大模型部署到梦中的计算机
 完成第三代计算机以上这些,花费了3年.
 陈平尝试以大模型创造的智能体,创造一个探险机器人,但是很快,探险机器人就发生了堕变,转头开始攻击陈平.陈平赶快把机器人拆散架,然后把计算机里面的大模型本体也封存.
 陈平绝望地躺在安全屋,或许真的没办法出去了.
 忽然,门口传来敲门声.
 陈平听到,寒毛一下竖了起来,一骨碌坐了起来,双眼紧盯木门.堕物也会进化的吗?甚至已经会敲门了.
 你是什么,来自哪里.女子说自己来自其他位面.偶然间才到了这里.她已经死了.不用过于担心.
 陈平接触到女子,情绪波动,又给外面的怪物输送了能量.
 女子名为月,是另一个位面的月神,麾下有梦魇之主.曾经创建梦境蛞蝓收付梦魇.但是后来,梦魇之主当起带路党,把世界献给深渊.
 女子要求陈平给予一定的造物权限,创造一种生物可以解决他的困境.但是陈平不搭理,害怕是神女录里面的角色.这样一想,马上创造出一个欲女,和女子长得一模一样.陈平手忙脚乱的用棒球打散该欲女.
 月打算创造一对梦境蛞蝓. 以梦为食.帮助陈平.
 陈平冥冥中感觉自己要死了,再过三个月就是30岁生日,或许过不了了.万幸的是,或许还能再过一个年.
 陈平醒来,决定发布Ping2.0,以及升级用Ping语言开发的各种软件,AI等,给世界留下点什么,好歹也算来过一遭. 之前只发布过Ping1.0.100
 接下来半个月,陈平不断把脑海中的代码写到现实世界. 1月20号,终于编写完毕,陈平发布了若干产品,包括AI大模型,对世界造成的轰动不提.
 期间,每天晚上,月都在向陈平学习.每天晚上,安全屋都要被攻破一次.陈平越来越嗜睡,把安全屋补好才能醒来.
 陈平身体每况愈下,被国家领导接见,陈平打算把产品献给国家.透露自己的死期.国家领导接见,安排亲属,勉励他,让他好好养病.陈平把一直和自己沟通的刘新宇推荐来掌握平公司.
 刘新宇,蓝桥杯金奖获得者,比陈平大10岁,程序员,从大厂出来创业从事新媒体行业,从事编程教学.对Ping语言感兴趣,三年前就开始和陈平沟通,是除陈平外对Ping语言掌握最深的.
 2月10号开始陈平一睡不醒,怎么呼叫也起不来,医生观察脑部活动,发现陈平一直在做梦和植物人状态切换.
 梦中最大的Boss,陈平的反面来了.
 2月14号,陈平打退怪物修补好安全屋后,打算向月表白.

 月在安全屋,对陈平额头一吻,我见过很多对恋人,认为爱情不过是生命的繁衍,因为社会而存在.如果与这种繁衍本能无关,那么爱情不存在.我问他们,你们为什么相爱,她们的回答都不让我满意.
 现在,陈平,你告诉我,你爱我.你能不能回答我,爱情是什么,你对我的爱该怎么描述.
 陈平: 6岁的时候,我坐在门口的小板凳上,看着太阳慢慢下山,身上的温度一寸寸丢失. 之后的每一刻,我都是这种感觉,我一个人漂浮在虚无中.我幻想过一个完美恋人,她懂我的一切,我也懂他的一切,我尊重她,怜惜她.她也一样.
 月,初见你那一刻,我就知道,你就是我的完美恋人.
 月狡黠一下:既然我是你的造物,由你创造而来,何必问我,你心里自然有答案.
 陈平也微微一笑:因为我的恋人,她是一个独立的个体,并不是我的附属品.

 月没有再说,只是要求开放造物权限.创造一对幻梦蛞蝓.这一对幻梦蛞蝓.
 月白如水.
 月:你认为我还是你幻想出来的吗?人不能创造认知外的事物.这对梦幻蛞蝓,存在的根基来于你,以噩梦为食,可化虚为实.
 陈平: 你,你真是异世界而来?
 月:虽然向我祈祷的恋人的回答并不让我满意,但我还是祝福他们,愿她们能同生共死,幸福美满.你知道为什么吗?
 陈平:为什么?
 月:因为这样对社会稳定有益.人们都和谐生活,多好啊.
 月看向陈平,向陈平嘴唇吻去.
 陈平感觉灵魂在共鸣,专心体会.
 月白了陈平一眼,手别乱动.继续仰头亲吻.
 良久,唇分.陈平感觉到来自灵魂深处的虚弱.
 月从嘴里吐出一颗莹白色珠子,放在掌心,"这颗界珠是我从你体内吸取而成,多么纯洁无瑕啊,可惜再也不属于你了,没有了这个珠子,你没有了造物的能力,不能从梦境醒来.因为你不再是梦主了."
 陈平一阵踉跄,后退一步,脸色惨白,"原来你的目的是这个"
 "那你还爱我吗?",月左手伸到胸前,掌心是那颗小小的界珠,珠子是透明的,散发着莹白色柔光.眼睛凑近珠子,看着界珠另一侧的陈平.
 月闭着眼睛,仔细感悟着手掌的珠子的能量与规则.
 陈平听着不远处的若有若无的低吼,看着眼前的飘飘欲飞的仙子,轻轻地说道,"爱,这个所谓界珠,算是我给你的彩礼了."
 "反正也要死了,能对你有帮助也是好事."
 月正打算右手指向界珠,睁眼看向陈平,闻言莞尔一笑."你不会死的.我可是月神,大千世界之主,有必要觊觎你这点还算不算真正世界的本源吗?"
 月右手勾起一根透明的线头,缓慢而稳定地把线牵引到残墙,在四面残墙,脚下和屋顶破木板上画了几个符号,一笔勾成.
 月睁开眼眸,看上去似乎越发的缥缈,拉起陈平一只手,把食指放到嘴里,轻轻一咬,然后将嘴里的鲜血吐到地上.整个屋子随即发出蒙蒙的亮光.
 "现在,这个屋子是进出梦境的唯一入口,睡着后可以控制来不来梦境,明天你可以睡个好觉了,这个屋子与你血肉相连,刚刚传了你几个法术,用来控制这个屋子,后续你慢慢掌握."
 月把之前创造的梦幻蛞蝓推入珠子,"等会,我会把界珠挂到空中.以后,有智慧生物进入梦境,才会有新堕念生成,而且会被转移到这个珠子,由蛞蝓吞食,这个虚幻的梦境会慢慢变成真实世界,外面那些堕物则会是无根之水,逐渐沉沦,被世界消解."

 月说完,把界珠吞入腹中,以腹部为中心全身开始散发光芒.慢慢向上漂浮.穿过屋顶.
 陈平赶紧打开门,跑到屋外,仰望向上的月.光芒中逐渐看不出人影,只能一团莹白色光团.
 白色光团越升越高,光芒逐渐黯淡,直至消失.周围重新归于漆黑的混沌.
 ...
 陈平仰望着光团消失的方向,一边驱散着零星游荡过来的堕物.
 忽然,于混沌中一轮圆盘,光芒炸开,照亮整个梦境,有的堕物在厮杀,两只堕物手还互相插在对方胸膛,有的堕物在无目的的游荡,不管在干什么,整个梦境的堕物都停了下来,全世界的生物都在仰头看着这一幕,大多数堕物都是第一次见到光
 ,这清清冷冷的光似乎在宣告者她的到来---月亮.
 陈平看着这一幕,内心激荡,这是他的月亮.
 紧接着,月亮马上光芒收缩,变成萤火一样的光,甚至还在逐渐变暗.
 陈平内心紧张,不敢喘气,怕一喘气,光就灭了.
 幸好,光时隐时灭,但还是倔强着不肯熄灭,微微摇曳,几乎不可见,但是一直在.

 下章
 陈平等了一夜,始终不见月回来.还是相信她的能力,作为曾经的神王,应该不会有什么问题.
 早上,a的重症监护室,巡查的护士,照例查看了监护仪器显示屏上的数据,然后换


 */


}
